/**
 * Copyright (c) 2004-2006 Regents of the University of California.
 * See "license-prefuse.txt" for licensing terms.
 */
package core.proto.vis;

import core.util.logging.constants.Action;
import core.proto.*;
import core.proto.search.LuceneIndexCreationUtil;
import core.proto.search.LuceneSearchTupleSet;
import core.proto.search.MyJSearchPanel;
import core.proto.search.QueryLoggerDocumentListener;
import core.proto.search.SearchPopupDocumentListener;
import core.proto.vis.action.FileOpenAction;
import core.proto.vis.action.FilterSearchResultEdgeAction;
import core.proto.vis.action.SetLockNodeAction;
import core.proto.vis.action.ShowSearchResultEdgeAction;
import core.proto.vis.action.TreeRootAction;
import core.proto.vis.action.ZoomToFitAction;
import core.proto.vis.action.assignment.EdgeColourAction;
import core.proto.vis.action.assignment.TextColourAction;
import core.proto.vis.action.util.Position;
import core.proto.vis.controls.*;
import core.proto.vis.predicate.EdgeMatchesSelectedWordOrInFocusGroup;
import core.proto.vis.predicate.ExcludeNonSearchResult;
import core.util.logging.UserLogWriter;
import core.util.logging.constants.Location;
import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.ItemAction;
import prefuse.action.RepaintAction;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.animate.PolarLocationAnimator;
import prefuse.action.animate.QualityControlAnimator;
import prefuse.action.animate.VisibilityAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.action.assignment.DataSizeAction;
import prefuse.action.assignment.FontAction;
import prefuse.action.filter.GraphDistanceFilter;
import prefuse.action.filter.VisibilityFilter;
import prefuse.action.layout.CollapsedSubtreeLayout;
import prefuse.action.layout.graph.RadialTreeLayout;
import prefuse.activity.SlowInSlowOutPacer;
import prefuse.controls.ControlAdapter;
import prefuse.controls.FocusControl;
import prefuse.controls.HoverActionControl;
import prefuse.controls.NeighborHighlightControl;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Schema;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.expression.AndPredicate;
import prefuse.data.expression.BooleanLiteral;
import prefuse.data.expression.Predicate;
import prefuse.data.io.GraphMLReader;
import prefuse.data.query.SearchQueryBinding;
import prefuse.data.search.KeywordSearchTupleSet;
import prefuse.data.search.LuceneSearcher;
import prefuse.data.search.PrefixSearchTupleSet;
import prefuse.data.search.SearchTupleSet;
import prefuse.data.tuple.DefaultTupleSet;
import prefuse.data.tuple.TupleSet;
import prefuse.render.*;
import prefuse.render.Renderer;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.GraphicsLib;
import prefuse.util.PrefuseLib;
import prefuse.util.display.DisplayLib;
import prefuse.util.ui.JFastLabel;
import prefuse.util.ui.JSearchPanel;
import prefuse.util.ui.UILib;
import prefuse.visual.EdgeItem;
import prefuse.visual.NodeItem;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.InGroupPredicate;
import prefuse.visual.expression.SearchPredicate;
import prefuse.visual.sort.TreeDepthItemSorter;

import javax.swing.*;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static app.constants.CollabVizConstants.*;
/**
 * Visualization for CollabViz based on radial tree
 *
 * @author Bill
 */
public class VizDemoRadial extends Display {

	private static boolean STAND_ALONE = false;

	private static final long serialVersionUID = 1L;

	private static final String TREE = "tree";
	private static final String NODES = "tree.nodes";
	private static final String EDGES = "tree.edges";
	private static final DefaultListModel listModel = new DefaultListModel();
	boolean noTabOpen = true;

	public DefaultListModel getListModel() {
		return listModel;
	}

	private static final Schema DECORATOR_SCHEMA = PrefuseLib
			.getVisualItemSchema();

	static {
		DECORATOR_SCHEMA.setDefault( VisualItem.INTERACTIVE, false ); // noninteractive
		DECORATOR_SCHEMA.setDefault( VisualItem.TEXTCOLOR,
				ColorLib.rgba( 100, 100, 100, 200 ) );
		DECORATOR_SCHEMA.setDefault( VisualItem.FONT,
				FontLib.getFont( "Tahoma", 11 ) ); // and not too big
	}

	ColorAction edgeColor;

	final GraphDistanceFilter distance;

	private static final String linear = "linear";

	public static final String BLUE_EDGE_SET = "_edgeset_";

	private LabelRenderer m_nodeRenderer;

	// ShapeRenderer m_nodeRenderer;

	private EdgeRenderer m_edgeRenderer;

	private String m_label = "label";

	private static Map<NodeItem, Position> nodePositionLock = new HashMap<NodeItem, Position>();

	private static Map<String, Integer> nameIdMapper = new HashMap<String, Integer>();

	JScrollPane edgeWordList; 

	public void setEdgeWordList(JScrollPane jp){
		this.edgeWordList = jp;
	}

	private static Node getNodeByName( Graph g, String name ) {
		Integer nodeId = nameIdMapper.get( name );
		if (nodeId == null){
			System.err.println("Error in viz: cannot find node with name=" + name);
			return null;
		}
		return g.getNode( nodeId );
	}

	private Map<String, List<EdgeItem>> attrMapping = new HashMap<String, List<EdgeItem>>();

	//	private List<EdgeItem> prevHighlightedEdges = new ArrayList<EdgeItem>();

	MyJSearchPanel searchPanel;

	public void setSearchPanel(MyJSearchPanel searchPanel){
		this.searchPanel = searchPanel;
	}

	// TODOwei clean up the constructor code; right now completely unreacable. 
	public VizDemoRadial( final Graph g, final String label, final int width,
			final int height ) {
		super( new Visualization() );

		createNameIdMapper( g );

		m_label = label;

		// -- set up visualization --
		m_vis.add( TREE, g );
		m_vis.setInteractive( EDGES, null, true );

		// -- set up renderers --

		m_nodeRenderer = new LabelRenderer( m_label );
		m_nodeRenderer.setRenderType( AbstractShapeRenderer.RENDER_TYPE_FILL );
		m_nodeRenderer.setHorizontalAlignment( Constants.CENTER );
		m_nodeRenderer.setRoundedCorner( 8, 8 );

		// m_nodeRenderer = new ShapeRenderer();

		m_edgeRenderer = new EdgeRenderer();

		DefaultRendererFactory rf = new DefaultRendererFactory( m_nodeRenderer );
		rf.add( new InGroupPredicate( EDGES ), m_edgeRenderer );

		Renderer polyR = new PolygonRenderer( Constants.POLY_TYPE_CURVE );
		( ( PolygonRenderer ) polyR ).setCurveSlack( 0.15f );

		rf.add( "ingroup('aggregates')", polyR );
		// rf.add( new InGroupPredicate( EDGE_DECORATORS ), new LabelRenderer(
		// "name" ) );
		// rf.add( new InGroupPredicate( NODE_DECORATORS ), new LabelRenderer(
		// "name" ) );

		m_vis.setRendererFactory( rf );

		// add edge labels
		// DECORATOR_SCHEMA.setDefault( VisualItem.TEXTCOLOR, ColorLib.gray( 0 )
		// );

		// add the decorator
		// removed for now, does not display right beside the hovered item
		// m_vis.addDecorators( EDGE_DECORATORS, EDGES, new HoverPredicate(),
		// DECORATOR_SCHEMA );
		// m_vis.addDecorators( EDGE_DECORATORS, EDGES, DECORATOR_SCHEMA );
		// DECORATOR_SCHEMA.setDefault( VisualItem.TEXTCOLOR, ColorLib.gray( 0 )
		// );
		// m_vis.addDecorators( NODE_DECORATORS, NODES, DECORATOR_SCHEMA );
		// -- set up processing actions --

		// colors
		// ItemAction nodeColor = new NodeColorAction( NODES );
		// int[] palette = new int[] { ColorLib.rgb( 255, 180, 180 ),
		// ColorLib.rgb( 190, 190, 255 ) };

		int[] palette = new int[]{ ColorLib.rgb( 168, 211, 255 ),
				ColorLib.rgb( 11, 249, 249 ), ColorLib.rgb( 90, 210, 250 ),
				ColorLib.rgb( 201, 147, 255 ), ColorLib.rgb( 174, 94, 255 ),
				ColorLib.rgb( 224, 181, 251 ), ColorLib.rgb( 255, 176, 216 ),
				ColorLib.rgb( 249, 164, 106 ), ColorLib.rgb( 253, 123, 156 ),
				ColorLib.rgb( 251, 130, 226 ), ColorLib.rgb( 164, 235, 156 ) };

		DataColorAction nodeColor = new DataColorAction( NODES, "type",
				Constants.NOMINAL, VisualItem.FILLCOLOR, palette );

		ItemAction textColor = new TextColourAction( NODES );
		edgeColor = new EdgeColourAction( EDGES );

		m_vis.putAction( "textColor", textColor );

		FontAction fonts = new FontAction( NODES, FontLib.getFont( "Tahoma",
				Font.BOLD, 10 ) );
		fonts.add( "ingroup('_focus_')",
				FontLib.getFont( "Tahoma", Font.BOLD, 15 ) );

		// recolor
		ActionList recolor = new ActionList();
		recolor.add( nodeColor );
		recolor.add( edgeColor );
		recolor.add( textColor );
		m_vis.putAction( "recolor", recolor );

		// repaint
		ActionList repaint = new ActionList();
		repaint.add( recolor );
		repaint.add( new RepaintAction() );
		m_vis.putAction( "repaint", repaint );

		// animate paint change
		ActionList animatePaint = new ActionList( 400 );
		animatePaint.add( new ColorAnimator( NODES ) );
		animatePaint.add( new RepaintAction() );
		m_vis.putAction( "animatePaint", animatePaint );

		// create the tree layout action
		RadialTreeLayout treeLayout = new RadialTreeLayout( TREE );
		treeLayout.setAutoScale( false );
		treeLayout.setRadiusIncrement( 125 );
		// treeLayout.setAngularBounds(-Math.PI/2, Math.PI);
		m_vis.putAction( "treeLayout", treeLayout );

		CollapsedSubtreeLayout subLayout = new CollapsedSubtreeLayout( TREE );
		m_vis.putAction( "subLayout", subLayout );

		ActionList hideAll = new ActionList();
		hideAll.add( new VisibilityFilter( new BooleanLiteral(false) ));
		m_vis.putAction("hideAll", hideAll);

		DataSizeAction edgeSize = new DataSizeAction( EDGES, "weight" );

		// EdgeLabelLayout edgeLabel = new EdgeLabelLayout( EDGE_DECORATORS );

		// NodeLabelLayout nodeLabel = new NodeLabelLayout( NODE_DECORATORS );

		// create the filtering and layout
		ActionList filter = new ActionList();
		filter.add( new TreeRootAction( TREE, nodePositionLock, listModel,
				attrMapping ) );
		filter.add( fonts );
		filter.add( treeLayout );
		// filter.add( edgeLabel );
		// filter.add( nodeLabel );
		filter.add( subLayout );
		filter.add( textColor );
		filter.add( nodeColor );
		filter.add( edgeColor );
		filter.add( edgeSize );
		m_vis.putAction( "filter", filter );

		// animated transition
		ActionList animate = new ActionList( 1250 );
		animate.setPacingFunction( new SlowInSlowOutPacer() );
		animate.add( new QualityControlAnimator() );
		animate.add( new VisibilityAnimator( TREE ) );
		animate.add( new PolarLocationAnimator( NODES, linear ) );
		animate.add( new ColorAnimator( NODES ) );
		animate.add( new RepaintAction() );
		m_vis.putAction( "animate", animate );
		//		 m_vis.alwaysRunAfter( "filter", "animate" );

		// lock nodes
		ActionList lock = new ActionList();
		SetLockNodeAction lockNode = new SetLockNodeAction( nodePositionLock );
		lock.add( lockNode );
		m_vis.putAction( "lock", lock );


		// restore all nodes/edges
		ActionList unhide = new ActionList();
		// distance between each node
		final GraphDistanceFilter levelInf = new GraphDistanceFilter( TREE,
				TREE, 5000 );
		//		unhide.add(new VisibilityFilter(new BooleanLiteral(true)));
		unhide.add( levelInf );
		m_vis.putAction( "unhide", unhide );

		ZoomToFitAction zoom = new ZoomToFitAction(this);
		m_vis.putAction( "zoom2Fit", zoom);

		distance = new GraphDistanceFilter(
				TREE, Visualization.FOCUS_ITEMS, noTabOpen ? 5000 : 1 );

		// filter and hide, only display the center node and its immediate neighbors. 
		ActionList filterAndHide = new ActionList();

		VisibilityFilter hideNonSearchResult = new VisibilityFilter(new ExcludeNonSearchResult(m_vis));
		filterAndHide.add( distance );
		filterAndHide.add(hideNonSearchResult);
		filterAndHide.add( filter );
		m_vis.putAction( "filterAndHide", filterAndHide );

		// show edges returned from results
		ActionList showEdgeResults = new ActionList();
		//		final GraphDistanceFilter level0 = new GraphDistanceFilter( TREE,
		//				Visualization.SEARCH_ITEMS, 0 );
		//		showEdgeResults.add( level0 );
		//		final ShowSearchResultEdgeAction edges = new ShowSearchResultEdgeAction();
		FilterSearchResultEdgeAction edges = new FilterSearchResultEdgeAction(m_vis.getGroup(EDGES));
		showEdgeResults.add( edges );
		m_vis.putAction( "show_edge_results", showEdgeResults );

		// show nodes returned from results
		ActionList showNodeResults = new ActionList();
		final GraphDistanceFilter level1 = new GraphDistanceFilter( TREE,
				Visualization.SEARCH_ITEMS, 0 );
		showNodeResults.add( level1 );
		m_vis.putAction( "show_node_results", showNodeResults );


		ActionList fileOpen = new ActionList();
		// don't add this if it's standalone
		if( !STAND_ALONE ) {
			fileOpen.add( new FileOpenAction( TREE ) );
		}
		m_vis.putAction( "file_open", fileOpen );

		// XXX: highlight nodes based on selected edge
		// ActionList edgeFocus = new ActionList();
		// NodeOrderAction nodeOrder = new NodeOrderAction( EDGES );
		// edgeFocus.add( nodeOrder );
		// m_vis.putAction( "edgeFocus", edgeFocus );

		// ------------------------------------------------

		// initialize the display
		setSize( width, height );
		setItemSorter( new TreeDepthItemSorter() );
		addControlListener( new DragControl() );

		// right-mouse-click zoom-to-fit
		addControlListener( new ZoomToFitControl() );

		// left-mouse-click zoom on selected on-focus node
		// XXX: Uncomment later, important
		// addControlListener( new ZoomToFitControl( Visualization.FOCUS_ITEMS,
		// 150, 0, Control.LEFT_MOUSE_BUTTON ) );

		addControlListener( new ZoomControl() );
		addControlListener( new PanControl() );
		addControlListener( new NeighborHighlightControl() );

		// focus control applies to only nodes
		//		FocusControl focusControl = new FocusControl( 1, "filter" );
		//		focusControl.setFilter( new InGroupPredicate( NODES ) );
		//		addControlListener( focusControl );

		// XXX: double click for opening files
		FocusControl fileControl = new FocusControl( 2, "file_open" );
		//		fileControl.setFilter( new InGroupPredicate( NODES ) );
		fileControl.setFilter( new AndPredicate(new InGroupPredicate(NODES), new ExcludeNonSearchResult(m_vis)));		
		addControlListener( fileControl );

		// XXX: highlight nodes based on edges
		// FocusControl edgeFocusControl = new FocusControl( 0, "edgeFocus" );
		// edgeFocusControl.setFilter( new InGroupPredicate( EDGES ) );
		// addControlListener( edgeFocusControl );

		// use filter and hide to show directly connected node
		FocusControl hideControl = new FocusControl( 1, "filterAndHide");
		//		hideControl.setFilter( new InGroupPredicate( NODES ));
		hideControl.setFilter( new AndPredicate(new InGroupPredicate(NODES), new ExcludeNonSearchResult(m_vis)));
		addControlListener( hideControl );


		addControlListener( new HoverActionControl( "repaint" ) {
			public void itemClicked(VisualItem item, java.awt.event.MouseEvent e) {
				if (item instanceof NodeItem){
					UserLogWriter.log2(((NodeItem) item).getString("name"), Location.Viz, Action.getClicks(e.getClickCount()));
				}else if (item instanceof EdgeItem){
					UserLogWriter.log2(getEdgeEndPts((EdgeItem) item), Location.Viz, Action.getClicks(e.getClickCount()));
				}
			}

			public void itemEntered(VisualItem item, java.awt.event.MouseEvent e){
				if (item instanceof NodeItem){
					UserLogWriter.log2(((NodeItem) item).getString("name"), Location.Viz, Action.HOVERENTER);
				}else if (item instanceof EdgeItem){
					UserLogWriter.log2(getEdgeEndPts((EdgeItem) item), Location.Viz, Action.HOVERENTER);
				}
			}

			public void itemExited(VisualItem item, java.awt.event.MouseEvent e) {
				if (item instanceof NodeItem){
					UserLogWriter.log2(((NodeItem) item).getString("name"), Location.Viz, Action.HOVEREXIT);
				}else if (item instanceof EdgeItem){
					UserLogWriter.log2(getEdgeEndPts((EdgeItem) item), Location.Viz, Action.HOVEREXIT);
				}
			}
		});
		addControlListener( new ToolTipControl( "name" ) );

		// ------------------------------------------------

		// filter graph and perform layout
		m_vis.run( "filter" );

		// maintain a set of items that should be interpolated linearly
		// this isn't absolutely necessary, but makes the animations nicer
		// the PolarLocationAnimator should read this set and act accordingly
		m_vis.addFocusGroup( linear, new DefaultTupleSet() );
		m_vis.addFocusGroup( BLUE_EDGE_SET, new DefaultTupleSet() );
		m_vis.getGroup( Visualization.FOCUS_ITEMS ).addTupleSetListener(
				new TupleSetListener() {
					public void tupleSetChanged( TupleSet t, Tuple[] add,
							Tuple[] rem ) {
						TupleSet linearInterp = m_vis.getGroup( linear );
						if( add.length < 1 ) {
							return;
						}
						linearInterp.clear();
						Node n = ( add[0] instanceof Node ) ? ( Node ) add[0]
								: null;
						for(; n != null; n = n.getParent() ) {
							linearInterp.addTuple( n );
						}
					}
				} );

		//        SearchTupleSet search = new PrefixSearchTupleSet( false );

		// lucene search
		setupLuceneSearcher ();
	}

	private void setupLuceneSearcher () {
		Directory indexDir;

		// Assume that index directory is downloaded onto the local computer. 
		try {
			indexDir = FSDirectory.getDirectory(new File(INDEX_DIRECTORY), false);
		} catch (IOException e) {
			throw new Error(e);
		}
		// Create read-only searcher. 
		LuceneSearcher searcher = new LuceneSearcher(indexDir, LuceneIndexCreationUtil.CONTENT_FIELD, true);
		LuceneSearchTupleSet search = new LuceneSearchTupleSet(searcher);
		Iterator<Tuple> itor = m_vis.getGroup(TREE).tuples();
		while(itor.hasNext()){
			search.addTupleToMap(itor.next());
		}

		// add search_items
		m_vis.addFocusGroup( Visualization.SEARCH_ITEMS, search );
		search.addTupleSetListener( new TupleSetListener() {
			public void tupleSetChanged( TupleSet t, Tuple[] add, Tuple[] rem ) {
				m_vis.cancel( "animatePaint" );
				TupleSet focusItems = m_vis.getGroup(Visualization.FOCUS_ITEMS);
				// update tree nodes here...
				if( t == null || t.getTupleCount() == 0 ) {

					if (focusItems.getTupleCount() > 0){
						edgeWordList.setVisible(true);

						if (noTabOpen){
							m_vis.run("unhide");
						}else{
							m_vis.run( "filterAndHide" );
						}
					}else{
						edgeWordList.setVisible(false);
						m_vis.run( "unhide" );
					}
				}
				else {
					// set focus to one of the search results. save previously focused item
					// if any so we can restore it afterwords. the reason for setting the focus
					// is purely cosmetic, otherwise the viz won't show correctly. this program
					// is a big pice of hackery. 
					if (focusItems.getTupleCount() > 0){
						Iterator itor = focusItems.tuples();
						if (! t.containsTuple((Tuple)itor.next())){
							edgeWordList.setVisible(false);
						}
					}
					m_vis.run( "show_node_results" );
					m_vis.run( "show_edge_results" );
				}
				m_vis.run( "recolor" );
				m_vis.run( "animatePaint" );
			}
		} );
	}

	private String getEdgeEndPts(EdgeItem item) {
		return "edge btw " + item.getSourceItem().getString("name") + " and " + item.getTargetItem().getString("name");
	}

	/**
	 * this starts at 0...
	 *
	 * @param g graph
	 */
	private void createNameIdMapper( Graph g ) {
		for( int i = 0; i < g.getNodeCount(); i++ ) {
			Node n = g.getNode( i );
			nameIdMapper.put( n.getString( "name" ), i );
		}
	}



	public void unhide(){
		if (m_vis.getGroup(Visualization.SEARCH_ITEMS).getTupleCount() > 0){
			m_vis.run("filterAndHide");
			m_vis.run("zoom2Fit");
		}else {
			m_vis.run("unhide");
		}
	}

	public void setHasNoTabOpen(boolean b){
		noTabOpen = b;
		distance.setDistance(b ? 5000 : 1); // how many levels to display. 
	}

	public void resizeAndRun( int width, int height ) {
		//		zoomToFit();
		setSize( width, height );
		m_vis.run( "recolor" );
		m_vis.run( "animatePaint" );
	}

	/**
	 * Set focus to the node with given name. 
	 * Allows an external caller to set the focus node.  
	 * @param nodeName
	 */
	public void externalSetFocus( String nodeName ) {
		//		nodeName = sanitizeNodeName( nodeName );
		Graph g = ( Graph ) m_vis.getGroup( TREE );
		Node f = getNodeByName( g, nodeName );

		if( f != null && g.containsTuple( f ) ) {
			m_vis.getGroup( Visualization.FOCUS_ITEMS ).setTuple( f );
			TupleSet searchRes = m_vis.getGroup(Visualization.SEARCH_ITEMS);

			if (searchRes.getTupleCount() > 0 && ! searchRes.containsTuple(f)){
				listModel.clear();
				this.edgeWordList.setVisible(false);
				m_vis.run("hideAll");
			} else{
				this.edgeWordList.setVisible(true);
				m_vis.run("filterAndHide"); // show only 1st degree neighbors. 
			}
		}
	}

	public void addEdgeColorPredicate (Predicate p, int color){
		edgeColor.add(p, color);
	}

	public void runAction (String action) {
		m_vis.run(action);
	}


	//	public void highLightEdgeByAttribute( String attribute ) {
	//		if( !attrMapping.containsKey( attribute ) ) {
	//			return;
	//		}
	//
	//		TupleSet edgeSet = m_vis.getGroup( VizDemoRadial.BLUE_EDGE_SET );
	//		edgeSet.clear();
	//
	//		// XXX: highlight or blue edge set?
	//
	//		// unhighlight previously highlighted items
	//		for( EdgeItem edge : prevHighlightedEdges ) {
	//			edge.setHighlighted( false );
	//		}
	//
	//		// now set the highlight we want to true
	//		for( EdgeItem edge : attrMapping.get( attribute ) ) {
	//			edge.setHighlighted( true );
	//		}
	//
	//		prevHighlightedEdges = attrMapping.get( attribute );
	//
	//		// blue edge set
	//		// for( EdgeItem edge : attrMapping.get( attribute ) ) {
	//		// edgeSet.addTuple( edge );
	//		// }
	//
	//		// repaint the visualization
	//		m_vis.run( "repaint" );
	//	}


	private void zoomToFit() {
		//		long duration = 10;
		//		int margin = 5;
		//
		//		Visualization vis = getVisualization();
		//		Rectangle2D bounds = vis.getBounds( TREE );
		//		GraphicsLib.expand( bounds, margin + ( int ) ( 1 / getScale() ) );
		//		DisplayLib.fitViewToBounds( this, bounds, duration );
	}

	// ----------------------------------------------------------------
	// ----------------------------Factories---------------------------
	// ----------------------------------------------------------------

	public static JPanel demo() {
		return demo( Util.getServerResourceAsStream(RADIAL_TREE_DATA_FILE), "name", 600, 600 );
	}

	public static JPanel demo(InputStream vizDataStream, final String label,
			final int width, final int height ) {
		Graph g = null;
		try {
			g = new GraphMLReader().readGraph( vizDataStream );
		} catch( Exception e ) {
			e.printStackTrace();
			System.exit( 1 );
		}
		return demo( g, label, width, height );
	}

	public static JPanel demo( Graph g, final String label, final int width,
			final int height ) {
		// create a new radial tree view
		final VizDemoRadial gview = new VizDemoRadial( g, label, width,
				height - 30 );
		Visualization vis = gview.getVisualization();

		// search within nodes
		// SearchQueryBinding sq = new SearchQueryBinding(
		// (Table) vis.getGroup( NODES ), label,
		// (SearchTupleSet) vis.getGroup( Visualization.SEARCH_ITEMS ) );

		// lucene search
		// note: this asks the search to use the CONTENT_FIELD 
		// as the search field, same as the lucene searching used
		// in the file directory component. however, the SearchTupleSet
		// used is set in the constructor. 
		final SearchQueryBinding sq = new SearchQueryBinding( vis.getGroup( TREE ),
				LuceneIndexCreationUtil.CONTENT_FIELD,
				( SearchTupleSet ) vis.getGroup( Visualization.SEARCH_ITEMS ) );

		final MyJSearchPanel search = new MyJSearchPanel((SearchTupleSet) vis.getGroup(Visualization.SEARCH_ITEMS), LuceneIndexCreationUtil.CONTENT_FIELD, true);
		search.setShowResultCount( true );
		search.setBorder( BorderFactory.createEmptyBorder( 5, 5, 4, 0 ) );
		search.setFont( FontLib.getFont( "Tahoma", Font.PLAIN, 11 ) );

		search.addUserLogClickListener(Location.Viz);

		search.addDocumentListener(new SearchPopupDocumentListener(search, "Clear search text to see more nodes"));
		search.addDocumentListener(new QueryLoggerDocumentListener(Location.Viz));

		gview.setSearchPanel(search);

		final JFastLabel title = new JFastLabel( "Attributes" );
		title.setPreferredSize( new Dimension( 350, 20 ) );
		title.setVerticalAlignment( SwingConstants.BOTTOM );
		title.setBorder( BorderFactory.createEmptyBorder( 3, 0, 0, 0 ) );
		title.setFont( FontLib.getFont( "Tahoma", Font.PLAIN, 16 ) );

		final Box box = new Box( BoxLayout.X_AXIS );
		box.add( Box.createHorizontalStrut( 10 ) );
		box.add( title );
		box.add( Box.createHorizontalGlue() );
		box.add( search );
		box.add( Box.createHorizontalStrut( 3 ) );

		final JList list = new JList( listModel );
		list.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
		list.setSelectedIndex( -1 );
		// Make our mouse event the only mouse event.  
		for(MouseListener l : list.getMouseListeners()){
			list.removeMouseListener(l);
		}
		list.addListSelectionListener( new ListSelectionListener() {
			public void valueChanged( ListSelectionEvent e ) {
				gview.runAction("repaint");
			}
		} );
		list.addMouseListener(new MouseAdapter(){
			public void mouseClicked (MouseEvent me) {
				int selection = list.locationToIndex(me.getPoint());
				if (selection != -1) {
					if (list.isSelectedIndex(selection)){
						UserLogWriter.log("word unselected in list: " + list.getSelectedValue(), Location.Viz, Action.UNSELECT);
						SwingUtilities.invokeLater(new Runnable(){
							public void run() {
								list.clearSelection();
							}
						});
					} else {
						list.setSelectedIndex(selection);
						UserLogWriter.log2("word selected in list : " + list.getSelectedValue(), Location.Viz, Action.SELECT);
					}
				}
			}
		});
		EdgeMatchesSelectedWordOrInFocusGroup colorPredicate = new EdgeMatchesSelectedWordOrInFocusGroup(list, gview.getVisualization().getGroup(BLUE_EDGE_SET), gview.attrMapping); 
		gview.addEdgeColorPredicate(colorPredicate, ColorLib.rgb( 0, 0, 0 ));

		final JScrollPane listScrollPane = new JScrollPane( list );

		listScrollPane.setSize((int) (Util.getVizWidth() * VIZWORDLIST_RATIO), Util.getVizHeight());
		listScrollPane.setPreferredSize(new Dimension((int) (Util.getVizWidth() * VIZWORDLIST_RATIO), Util.getVizHeight()));

		listScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
		JLabel columnHeader = new JLabel("Connected words");
		columnHeader.setFont(new Font("Arial", Font.ITALIC, 12));
		JPanel labelPanel = new JPanel();
		labelPanel.add(columnHeader);
		listScrollPane.setColumnHeaderView(labelPanel);
		listScrollPane.setVisible(false);
		//        SwingUtilities.invokeLater(new Runnable() {
		//
		//			@Override
		//			public void run() {
		//				//listScrollPane.setVisible( false );
		//			}
		//        	
		//        });
		//        listScrollPane.setVisible( false );

		final JPanel panel = new JPanel( new BorderLayout() );


		panel.add( gview, BorderLayout.CENTER );
		panel.add( listScrollPane, BorderLayout.WEST );
		//		panel.add( box, BorderLayout.SOUTH ); 
		panel.add( box, BorderLayout.NORTH );

		Color BACKGROUND = Color.WHITE;
		Color FOREGROUND = Color.DARK_GRAY;
		UILib.setColor( panel, BACKGROUND, FOREGROUND );

		gview.addControlListener( new ControlAdapter() {
			public void itemEntered( VisualItem item, MouseEvent e ) {
				// if this is an edge
				if( item instanceof EdgeItem ) {
					title.setText( "Attributes: " + item.getString( "name" ) );
					return;
				}
				if( item.canGetString( label ) ) {
					title.setText( item.getString( label ) );
				}
			}

			public void itemExited( VisualItem item, MouseEvent e ) {
				title.setText( " " );
			}

			public void itemClicked( VisualItem visualItem, MouseEvent mouseEvent ) {
				if( visualItem instanceof NodeItem ) {
					if( !listScrollPane.isVisible() ) {
						listScrollPane.setVisible( true );
						panel.revalidate();
					}
					panel.repaint();
				}
			}
		} );

		gview.setEdgeWordList(listScrollPane);

		return panel;
	}

	//
	//
	// ----------------------------------------------------------------
	// ---------------------- MAIN RUNNER -----------------------------
	// ----------------------------------------------------------------
	//
	//
	public static void main( String argv[] ) {
		STAND_ALONE = true;

		String infile = RADIAL_TREE_DATA_FILE;
		String label = "name";

		if( argv.length > 1 ) {
			infile = argv[0];
			label = argv[1];
		}

		InputStream stream;
		if (infile.equals(RADIAL_TREE_DATA_FILE)){
			stream = Util.getServerResourceAsStream(infile);
		} else {
			try {
				stream = new FileInputStream(new File(infile));
			} catch (FileNotFoundException e) {
				throw new Error(e);
			}
		}

		UILib.setPlatformLookAndFeel();

		JFrame frame = new JFrame(
				"i n f o v i z | r a d i a l v i e w - d e m o" );
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.setContentPane( demo( stream, label, 600, 600 ) );
		frame.pack();
		frame.setVisible( true );
	}

} // end of class RadialGraphView